home *** CD-ROM | disk | FTP | other *** search
- ;**************************************************************************
- ; GUSFILL.ASM
- ;
- ; This example program does sends some stuff to/from the GUS via DMA.
- ; The code here is explained in detail. It is a good example for someone
- ; who would like to start coding for thier GUS. See IRQ handler for
- ; details on handling interrupts from the GUS.
- ;
- ;
- ; to compile;
- ;
- ; tasm gusfill.asm or ml /c /Dmasm gusfill.asm
- ; dlink gusfill.obj ..\lib\dma.obj ..\lib\gus.obj
- ;
- ; note that this program requires the GUS.ASM and DMA.ASM library
- ;
- ;
- ; Written by Adam Seychell
- ;**************************************************************************
- .386
- .model flat ,C
- .stack
-
-
- include dma.inc
- include gus.inc
-
-
- ;-------------------------------------------------------------------------
- ; DISPLAY A STRING ON SCREEN WITH CARAGE RETURN
- ;
- ; Usage: Writeln <' string to display '>
- ;
- ;
- ;-------------------------------------------------------------------------
- writeln MACRO STRING_
- LOCAL TEXT3_S,skip_wrln
- push eax
- push edx
- jmp skip_wrln
- TEXT3_S DB STRING_,13,10,36
- skip_wrln:
- mov EDX,offset TEXT3_S
- mov ah,9
- Int 21h
- pop edx
- pop eax
- ENDM
-
-
- gf1_clock equ 617400
-
- .DATA
-
- DMA_buffer_phys dD ?
- DMA_buffer_addr dD ?
- DMA_DRAM_address dD ?
- GUS_MEMORY dD ?
- DMA_Play_Chan dB ?
- DMA_Samp_Chan dB ?
- DMAPlay_TC dB ?
- gf1_IRQ dB ?
- midi_IRQ dB ?
-
-
- .CODE
-
- The_Start:;**************** ENTRY POINT OF THE PROGRAM ************
-
-
- ;********* Allocate a 16KB DMA buffer ***************
- mov ax,0EE41h ; call a DOS32 service
- int 31h
- jc error_dma_buffer
- mov DMA_buffer_addr,edx ; save the address
- mov DMA_buffer_phys,ebx
-
-
-
-
- ;********* Get the Default GUS settings from the "ULTRASND=" string *********
- Call GetUltraConfig ; See GUS.ASM
- jc Error_EnvString
-
- mov DMA_Play_Chan,CL ; save DMA channels
- mov DMA_Samp_Chan,CH
- mov gf1_IRQ,BL
- mov midi_IRQ,BH
-
-
- ;************ Fully reset the Ultraosund *************************
-
- ;Expects BL=gf1 IRQ
- ; BH=midi IRQ
- ; CL=dram DMA chan
- ; CL=adc DMA chan
- call Ultrasound_Reset ; DX=base port
- jc bad_reset
-
- ; returns EDI = gus memory size
- mov GUS_MEMORY,EDI
-
-
- writeln 'Filling Ultrasound''s DRAM via DMA ( # = 16Kb ).'
-
-
-
- ;************** Set the IRQ vector for the Ultrasound *****************
- mov edx,offset GUS_ISR
- mov cx,cs ; CX:EDX = selectro:offset
- mov bl,gf1_IRQ ; Convert IRQ to interrupt
- cmp bl,8 ; number
- jb Jpic1
- add bl,60h
- Jpic1: add bl,8
- mov ax,0205h ; Set interrupt vector
- int 31h
-
-
-
-
- ; ****** program the 8237 DMA contoller *************
- mov al,01011000b ; DMA mode register
- mov ah,DMA_Play_Chan ; Channel number ( 0..7 )
- mov ecx,04000h ; Bytes to transfer
- mov ebx,DMA_buffer_phys ; Physical base address
- call DMA_Setup ; Do it ( see DMA.ASM )
-
- ; Note: Mode register bit 4 is set for DMA auto initalizing mode.
- ; This means that the Address and Count registers don't have to be
- ; reprogramed after the DMA has finished transfering.
- ; i.e once the DMA has finished transfering it will pulse the TC
- ; line and also reload the Address and Count registers. The GUS card
- ; will invoke the hardware interrupt when it detects a signal on TC line.
- ;
-
- mov DMA_DRAM_address,0 ; Reset fill counter
-
-
- FILL_GUS_LOOP:
- ;********* Set the GUS's DMA DRAM staring address register *****
- ; note the wierdness when a 16bit dma channel is used
- ;
-
- mov dx,gf1_reg_select
- mov al,042h ; Set DMA Start Address
- out dx,al
-
- mov eax,DMA_DRAM_address
- test DMA_Play_Chan,100b
- jz _8bitDMA
- ; ---- do 16 bit DMA address translation ( see the SDK ) -----
- mov edi,eax
- shr eax,1
- and eax,01ffffh ; zero out bit 17..19
- and edi,0c0000h ; get bits 18 and 19
- or eax,edi
- _8bitDMA:
- shr eax,4
- mov dx,gf1_data_low
- out dx,ax ; Set the damn register
-
-
- ;********* Set the gf1 DMA Control Register ****************
-
-
- mov al,041h ;Set DRAM DMA Control Register
- mov dx,gf1_reg_select
- out dx,al
-
- mov ah,DMA_Play_Chan
- and ah,100b
- mov al,00100001b ;Enable DMA, read, 650KB/s, 16bit data, 16/8bit DMA
- or al,ah
- mov dx,gf1_data_high
- out dx,al
-
- ; The DMA cycle will now start..........
-
-
-
-
-
- ;********************** Wait around for the DMA to finish **************
- mov ecx,50000h
- waitTC: test DMAPlay_TC,1 ; pole the flag
- loopz waitTC
- jz timoutERROR
- mov DMAPlay_TC,0
-
-
- mov dl,'#'
- mov ah,2
- int 21h
-
- add DMA_DRAM_address,4000h
- mov eax,GUS_MEMORY
- cmp eax,DMA_DRAM_address
- ja FILL_GUS_LOOP ; loop until all memory filled.
-
- ;****************** DRAM DMA transfering is completed ********************
- ;
- ; The DMA will no longer be transfering because the GUS's DRAM DMA control
- ; register has not been re-programed after the last DMA cycle. Once the
- ; TC has been reached the GUS automaically clears bit 0 of the control
- ; register to stop DMA from transfering. That is why the above loop
- ; must reload the DRAM DMA control register for each cycle.
- ;
-
-
- exit:
-
- ; ****** program the 8237 DMA contollers *************
- ; Must turn of auto initalizing ????
- ; Sometimes my computer goes to about 1/8th of it's speed
- ; when if DMA auto initalizing bit is left on. Only a hardware reset
- ; will make it go back to normal. wierd yea?
-
- mov al,00001000b ; DMA mode register
- mov ah,DMA_Play_Chan ; Channel number ( 0..7 )
- call DMA_Setup
-
- ; ****** Terminate Program *************
- mov ax,4C00h
- int 21h
-
-
-
-
-
- ;_________________________ ERROR MESSAGES ________________________________
- timoutERROR:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<
- writeln 'ERROR: got no IRQ form GUS'
- jmp exit
- Error_EnvString:;<<<<<<<<<<<<<<<<<<<<<<<<
- writeln 'Cannot find ''ULTRASND='' environment string or has invalid settings'
- jmp exit
- bad_reset:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
- writeln 'Could not detect and Ultrasound on this computer '
- jmp exit
- error_dma_buffer:;<<<<<<<<<<<<<<<<<<<<<<<
- writeln 'Not enough memory for 16Kb DMA buffer'
- jmp exit
-
-
-
-
-
-
- comment ~
- *************************************************************************
- The Gravis Ultrasound IRQ handler
-
- The GUS's IRQ handler can be a tricky thing to do. As good as the SDK
- ( Software Developers Kit) may be, I feel it doesn't not go into enough
- detail on how to correctly handle IRQ's sent from the GUS. I will make
- an attempt it here exaplaining.
-
- The following routine is a general IRQ handler and may be used for
- haneling all types of IRQ's generated from the GUS. This program only
- uses the DMA playback channel with all other IRQ sources turned off,
- therefore this routine below only needs to handle IRQ's generated
- from the TC ( Terminal Count ) send by the playback DMA channel.
-
-
- Any of the following pices of the GUS's hardware can generate an IRQ.
-
- * When a voice's current address is greater or equal to it's ending
- address.
- * When a voice's current volume is greater or equal to it's
- ending volume.
- * Timer 1 reached has a counting value of 0FFh.
- * Timer 2 reached has a counting value of 0FFh.
- * The playback DMA channel has finished transfering data.
- * The sampling DMA channel has finished transfering data.
-
-
- Note that if a voice has LOOPING DISABLED then the voice's current
- location will stop once it has reached it's ending location. This will
- cause an IRQ ( assuming they are enabled for that voice) and will
- continue to generate an IRQ each time the GF1 services that voice.
- ( i.e IRQ's genrated at a rate equal to 617400Hz/number of active voices)
- This is beacuse the current location is greater than or equal to the
- the ending location. To stop this from happening the IRQ handler
- must disable IRQ's for that voice if looping is set disabled. If looping is
- enabled then we may leave the voices IRQ's enabled beacue the looping
- effect returns the current location directly back to the starting location
- once it hits the ending location. Therfore with looping enabled only
- a single IRQ is generated at each time the voice loops.
- This effect of repetitive IRQ generation for voices with looping disabled
- also applies to volume ramping when the volume ramp looping is disabled.
- Note carfully in the following routines on how the voice wave table and
- volume ramping IRQs are handled so that repetitive IRQs are avoided.
-
-
-
- *************************************************************************
- ~
- GUS_ISR PROC
-
- ; setup usual IRQ stuff.
-
- push ds ; Save all registers used
- pushad
- mov ax,_TEXT ; Load DS with data selector
- mov ds,ax
-
- LOOP_irq_handler: ;<---- jumps here after particular IRQ is handled.
-
- ;
- ; First determine sources of the IRQ's by looking at the irq status register.
- ; port 2x6h.
- ;
- mov dx,gf1_irq_status
- in al,dx
- mov irq_status,al
-
-
- ; If all bits cleared then we are ready to exit the interrupt
- ; routine.
- test al,11111111b
- jz exit_ISR
-
-
- ;********************* MIDI Transmit ***********************
- test irq_status,00000001b
- jz done_MIDI_Tx
-
- ; midi transmit code can go here.....
- ;
- ; call midi_xmit_func
-
-
- ;********************* MIDI Receive ***********************
- done_MIDI_Tx:
- test irq_status,00000010b
- jz done_MIDI_Rx
- ;
- ; Read MIDI data port. Note: this read also clears IRQ pending
- ; so more midi receive IRQ's are avalible.
-
- mov dx,midi_data
- in al,dx
-
- ; midi recieve code can go here.....
- ;
- ; call midi_recv_func
-
-
-
- done_MIDI_Rx:
- test irq_status,00000100b
- jz done_Timer_1
- ;********************* Timer 1 ***********************
- ;
- ; Pulse IRQ enable bit of timer controll register bit from 0 to 1.
- ; If this is not done then no more IRQ's will be made from timer 1.
- ;
- mov dx,gf1_reg_select
- mov al,45h
- out dx,al
- mov dx,gf1_data_high
- in al,dx
- and al,NOT 0100b ; clear timer 1 IRQ enable
- out dx,al
- or al, 0100b ; set timer 1 IRQ enable
- out dx,al
-
- ;
- ; timer 1 code can go here........
- ;
- ; call timer1_func
-
-
- done_Timer_1:
- test irq_status,00001000b
- jz done_Timer_2
- ;********************* Timer 2 ***********************
- ;
- ; Pulse IRQ enable bit of timer controll register bit from 0 to 1.
- ; If this is not done then no more IRQ's will be made from timer 2.
- ;
- mov dx,gf1_reg_select
- mov al,45h
- out dx,al
- mov dx,gf1_data_high
- in al,dx
- and al,NOT 1000b ; clear timer 2 IRQ enable
- out dx,al
- or al, 1000b ; set timer 2 IRQ enable
- out dx,al
-
- ;
- ; timer 2 code can go here........
- ;
- ; call timer2_func
-
-
-
-
- ;************ Voice Wave Table and/or Volume Ramp **********************
- done_Timer_2:
- test irq_status,01000000b or 00100000b
- jz done_WT_VR
-
- Call Service_Voice_Interrupt ; see routine below
-
-
-
-
- ;********************* DMA TC ( sample and/or playback ) ***************
- done_WT_VR:
- test irq_status,10000000b
- jz LOOP_irq_handler
-
- ;
- ; Read the DRAM DMA control register. Reading this register also
- ; clears the IRQ pending bit which allows another DRAM IRQ to
- ; occurr. If we do not read this then DMA DRAM will not generate
- ; anymore IRQs.
- ;
- mov dx,gf1_reg_select
- mov al,041h ; DRAM DMA Control Register
- out dx,al
- mov dx,gf1_data_high ; read
- in al,dx
- test al,01000000b ; was it a DRAM DMA IRQ
- jz check_sample_TC ; if not then do SAMPLE DMA IRQ
-
- ;
- ; code can go here for playback DMA terminal count.......
- ;
- or DMAPlay_TC,1
- ; call dram_dma_tc_func
- jmp LOOP_irq_handler
-
-
-
- check_sample_TC:
- ;
- ; Read the SAMPLE DMA control register. Reading this register also
- ; clears the IRQ pending bit which allows another IRQ.
- ;
- mov dx,gf1_reg_select
- mov al,049h
- out dx,al
- mov dx,gf1_data_high
- in al,dx
-
- ;
- ; code can go here for sampling DMA terminal count.......
- ;
- ; ..... ..... ..
-
- ; call sample_dma_tc_func
- jmp LOOP_irq_handler
-
-
- ; Exit the interrupt routine.
- exit_ISR:
- mov al,020h ; Send EOI command to both PICs (8259)
- out 20h,al
- out 0A0h,al
- popad
- pop ds
- iretd
-
- irq_status DB 0
-
- GUS_ISR ENDP
- ;******************** End of Gravis ultrasound IRQ handler ******************
-
-
-
-
-
- comment ~
- ******************************************************
- Voice Interrupt Handler
-
- This routine handles IRQs generated from any of the voices.
- In other words when voice has reched it's ending location or it's
- ending volume.
- It's possible that multiple voices could interrupt at the same time
- or interrupt while still services other voices interrupts. The handler
- must keep on reading the IRQ source register( reg number 8Fh) until
- all voices are cleared of interrupt requests ( i.e until bits 7 & 6
- are both set).
-
- ----- IRQ Source Register format ------
- bit 0..4 = interrupting voice
- bit 5 = 1 ( ingnore this )
- bit 6 = volume ramp irq pending
- bit 7 = volume ramp irq pending
-
- *******************************************************~
- Service_Voice_Interrupt PROC
-
- Local Wave_Ignore,Volume_Ignore :dword
-
- mov Volume_Ignore,0
- mov Wave_Ignore,0
- SERVICE_LOOP:
- mov dx,gf1_reg_select ; Read the IRQ source register
- mov al,8Fh
- out dx,al
- add dl,2
- in al,dx
- mov ch,al
- and ch,11000000b
- cmp ch,11000000b ; check if any IRQs are left
- je FIFO_empty ; if not then exit routine
- and al,011111b
- mov cl,al ; Save voice number in CL
- mov ebx,1
- shl ebx,cl
-
- ;
- ; CL = voice we need to service.
- ; CH = bits 6 & 7 of irq source register
- ; EBX = 1 SHL voice number .
-
- push ECX ; save these registers.
- push EBX
-
- ; Did Wave Table cause IRQ ?.
- ;
- test CH,10000000b ; look at bit 7 of IRQ source
- jnz was_not_a_WT
-
- ; see if voice has already been service. If so then ignore it.
- ;
- test Wave_Ignore,ebx
- jnz was_not_a_WT
- or Wave_Ignore,ebx ; mark voice to ingnore in future.
-
- ; your code goes here to handle the particular voices wave table
- ; interrupt. NOTE: if volume looping is disabled or rollover is enabled
- ; then you must disable the voice's IRQs ( by clearing bit 5 of Voice
- ; Control register ) or you'll be flooded with IRQ's.
-
- ; .......wave table irq code.......
-
-
-
-
-
- was_not_a_WT:
- ; Did volume ramp cause IRQ ?.
- ;
- pop ECX ; restore these registers
- pop EBX
- test CH,01000000b ; look at bit 6 of IRQ source
- jnz was_not_a_Vol
-
- ; see if voice has already been service. If so then ignore it.
- ;
- test Volume_Ignore,ebx
- jnz was_not_a_Vol
- or Volume_Ignore,ebx ; mark voice to ingnore in future
-
- ; your code goes here to handle the particular voices volume ramp
- ; interrupt. NOTE: if volume looping is disabled then you must
- ; disable the voice's volume ramp IRQs ( by clearing bit 5 of Volume
- ; Ramp Control register ) or you'll be bombarded with IRQ's.
-
- ; .......volume ramp irq code.......
-
-
-
-
-
- was_not_a_Vol:
- jmp SERVICE_LOOP ; contiue servicing interrupts.
- ; until there all gone.
-
-
- FIFO_empty: ;<---- jumps here when bits 6 & 7 of the IRQ source
- ; register are both set, i.e no irq's waiting.
-
- Ret ; return to main IRQ handler
-
- Service_Voice_Interrupt ENDP
- ;********************* End of voice interrupt handler **********************
-
-
-
- END The_Start
-